10 LoadButton

Próxima Atividade
  • Sugerir melhoria
Play
Current Time 0:00
/
Duration Time 0:00
Remaining Time -0:00
Stream TypeLIVE
Loaded: 0%
Progress: 0%
0:00
Fullscreen
00:00
Mute
Playback Rate
  • 2x
  • 1.5x
  • 1.2x
  • 1x
  • 0.8x
  • 0.7x
1x
Subtitles
  • subtitles off
Captions
  • captions off
Chapters
  • Chapters
HD
Desculpe-nos pelo transtorno, mas este video não pode ser carregado, tente novamente.

Transcrição

Criaremos o botão "Load more" como sendo um componente em "photo-list", portanto voltaremos ao terminal e pausaremos o Angular CLI. Usaremos o atalho ng g c photos/photo-list/load-button, em que c se refere a component e g a generate para gerarmos o "load-button".

A primeira propriedade que ele terá é hasMore, de tipo boolean, que começará com false. Isto nos ajudará a saber se há mais elementos a serem exibidos, ou não. Em load-button.component.ts, teremos:

export class LoadButtonComponent implements OnInit {

    hasMore: boolean = false;

    constructor() { }

    ngOnInit() {
    }

}

Apagaremos o conteúdo de load-button.component.html e incluiremos o seguinte código:

<div class="text-center">
    <button class="btn btn-primary">Load more</button>
</div>

<p class="text-center text-muted">No more data to load</p>

Com isso, teremos uma <div> centralizada, que exibirá nosso botão, e usamos a classe btn do Bootstrap, bem como btn-primary, para que ele tenha destaque, e os textos "Load more" e "No more data to load". Quem for utilizar o componente em load-button.component.ts receberá o hasMore, um booleano. Em selector, não podemos esquecer de remover um "p" de app-load-button, mantendo simplesmente ap-load-button.

Se queremos passar um parâmetro via forma declarativa do componente, precisaremos utilizar @Input(), que importaremos. E em photo-list.component.html, já que incluiremos o botão após a listagem de imagens, adicionaremos:

<ap-load-button></ap-load-button>

Salvaremos, subiremos a aplicação no terminal e verificaremos o resultado no navegador, um botão azul de cantos arredondados contendo o texto "Load more" centralizado, junto a "No more data to load" logo abaixo, sendo que isto deveria ser condicional — se há dados a serem exibidos, o botão deve aparecer. Caso contrário, apenas o texto de baixo.

Para lidarmos com esta situação, incluiremos na <div> de load-button.component.html a diretiva *ngIf, que já conhecemos, e a condição será hasMore. Significa que, se hasMore ("há mais"), que começa com valor falso, será exibido o botão. Se não, o texto.

O Angular permite uma estrutura if/else no template, e aquele que estiver no else precisará estar dentro da tag <ng-template>. Assim, adicionaremos nele uma variável de template capaz de identificá-lo, podendo ter um nome qualquer. Neste caso, será utilizado messageTemplate.

Poderíamos colocar esta variável no botão, mas optaremos por fazê-lo no template.

<div class="text-center" *ngIf="hasMore; else messageTemplate">
    <button class="btn btn-primary">Load more</button>
</div>

<ng-template #messageTemplate>
    <p class="text-center text-muted">No more data to load</p>
</ng-template>

Salvaremos e retornaremos ao navegador, que exibe "No more data to load" ao final da página, pois o valor padrão de hasMore é false, então, o trocaremos por true e testaremos novamente. Desta vez, o texto será substituído pelo botão.

Em photo-list.component.html será necessário realizar Data binding em uma propriedade, para que seja indicado se há mais ou não. Em photo-list.component.ts, também incluiremos a propriedade hasMore, que começará recebendo false:

photos: Photo[] = [];
filter: string = '';
debounce: Subject<string> = new Subject<string>();
hasMore: boolean = false;

E então, faremos em photo-list.component.html uma associação de hasMore com a expressão homônima:

<ap-load-button [hasMore]="hasMore"></ap-load-button>

Ou seja, a inbound property do botão fará um Data binding com o hasMore de photo-list.component.ts. Cada vez que clicarmos no botão, um load ("carregamento") será realizado, e então verificaremos se os dados chegaram. Caso positivo, significa que hasMore se mantém true. Caso contrário, e se tivermos um array vazio, é porque fomos além de sua capacidade.

Portanto, precisaremos do photoService de volta, para que seja acessado a cada clique do botão. Além disso, criaremos a propriedade currentPage de tipo number, que começará com 1, e userName de tipo string, pois precisaremos saber disso toda vez que os dados forem lidos.

hasMore: boolean = false;
currentPage: number = 1;
userName: string = '';

constructor(
    private activatedRoute: ActivatedRoute,
    private photoService: PhotoService
) { }

Agora, no ato de pegarmos os dados, indicaremos que this.userName recebe this.activatedRoute.snapshot.params.userName, e em breve entenderemos o porquê de guardarmos tantos valores. Também criaremos o método load(), que chamará this.photoService.listFromUserPaginated. Usaremos ++ pois, quando carregamos a página pela primeira vez, o Resolver já carregou a primeira página, então quando damos load é porque queremos carregar a segunda. Trata-se de um pré-incremento, e em seu subscribe() receberemos photos.

Também em subscribe() fazemos uma desestruturação, ou Destructuring, na verdade, um Spread Operator, para informar que photos será desmembrado como se passássemos os dados de cada item retornado do back end. Testaremos se photos.length é diferente de this.hasMore e, neste caso, seu valor será false.

ngOnInit(): void {
    this.userName = this.activatedRoute.snapshot.params.userName;
    this.photos = this.activatedRoute.snapshot.data['photos'];
    this.debounce
        .pipe(debounceTime(300))
        .subscribe(filter => this.filter = filter);
}

ngOnDestroy(): void {
    this.debounce.unsubscribe();
}

load() {
    this.photoService
        .listFromUserPaginated(this.userName, ++this.currentPage)
        .subscribe(photos => {
            this.photos.push(...photos);
            if(!photos.length) this.hasMore = false;
        });
}

Feito isso, vamos substituir false de hasMore: boolean = false para true. Ao carregarmos a página, o botão aparecerá mas, quando clicado, o método load() será chamado, o que alterará o estado do componente, podendo ou não alterar o hasMore. Precisaremos acessar photo-list.component.html e chamar este método por meio de um binding para o evento click, usando parênteses.

<ap-load-button
    (click)="load()"
    [hasMore]="hasMore">
</ap-load-button>

Salvaremos tudo e voltaremos ao navegador para testarmos. Quando clicamos no botão, a mensagem "No more data to load" é exibida, e aparentemente funciona bem. Na tag <ap-photos> de photo-list.component.html realizamos um Data binding de photos para a propriedade photos de photo-list.component.ts, da classe PhotoListComponent.

O problema é que quando fazemos o push(), a cada item incluso ali dentro, o outro componente não sabe que photos é alterado. Isto porque não modificamos a referência this.photos. Estamos mudando os itens da lista de imagens, entretanto o photos.component.ts não sabe que photos mudou. Isso só aconteceria se fosse atribuído outro valor nesta propriedade.

Para resolvermos isso, em load() de photo-list.component.ts, usaremos this.photos = this.photos.concat(photos) em vez de this.photos.push(...photos). O concat() acessará a lista de imagens existente (this.photos) e a concatenará com a lista photos, gerando outra, que será uma nova referência. Com isto, o mecanismo de Change detection do Angular funcionará.

Se salvarmos e voltarmos ao navegador, ao clicarmos no botão, o restante das imagens da lista será apresentado. E se clicarmos mais uma vez, o texto "No more data to load" aparecerá.

Entendemos que precisamos nos atentar a estes casos de Data binding, em que o Angular só detecta alteração em uma inbound property quando há uma nova atribuição nela.

Tirar dúvida Próxima Atividade
ícone Angular parte 1: Fundamentos

Angular parte 1: Fundamentos

100%
Aula06 de 07

Melhorando a experiência do usuário

Atividades 10 de 11

  • 01 Projeto da aula anterior
  • 02 Binding de eventos 11min
  • 03 Pipe e implementação 14min
  • 04 Criando nosso próprio pipe
  • 05 Resolvers 12min
  • 06 Implementando Resolvers
  • 07 RxJS e seu Subject 12min
  • 08 Padrão debounce com RxJS
  • 09 Paginação de dados 07min
  • 10 LoadButton 16min
  • 11 Consolidando seu conhecimento

Aulas

Outros Links

Fórum Trocar Curso
Gravatar de Marcos Katsumi Kay

Marcos Katsumi Kay 34.4k xp

Logo da Alura